/* ppmtopcx.c - read a portable pixmap and produce a PCX file
**
** Copyright (C) 1990 by Michael Davidson.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include <stdio.h>
#include <stdlib.h>
#include <appkit/NXBitmapImageRep.h>
#include <streams/streams.h>
#include "pcxlib.h"
#include "kludge.h"

#define	MAXCOLORS	256
#define	MAXPLANES	4

#define cht					0
#define ppm_lookupcolor(a,b)	0

typedef void (* vfunptr)( int, int, unsigned char*, int, int );

/*
 * Pointer to function returning an int
 */
static void PCXEncode(		NXStream	* fp, 
							int 			GWidth, 
							int 			GHeight, 
							int 			Colors, 
							unsigned char	Red[], 
							unsigned char	Green[], 
							unsigned char	Blue[], 
							vfunptr 		GetPlanes);
static void PutPlane (			NXStream	*fp, 
							unsigned char	*buf, 
							int 			Size );
static void ReadPlanes(		int			y, 
							int 			width, 
							unsigned char	*buf, 
							int 			planes, 
							int 			bits);
static void Putword( 			int 			w, 
							NXStream	*fp);
static void Putbyte( 			int 			b, 
							NXStream	*fp );

static unsigned char		*picData;

void pcxwrite(NXStream 		*ifp, 
			unsigned char 	*data, 
			unsigned char		r[256],
			unsigned char		g[256],
			unsigned char		b[256],
			int				width,
			int				height,
			int				colors)
{
	/* All set, let's do it. */
	picData = data;
	PCXEncode(ifp, width, height, colors, r, g, b, ReadPlanes );
}

/*****************************************************************************
 *
 * PCXENCODE.C    - PCX Image compression interface
 *
 * PCXEncode( FName, GHeight, GWidth, Colors, Red, Green, Blue, GetPlanes )
 *
 *****************************************************************************/

/* public */

static void PCXEncode(		NXStream	* fp, 
							int 			GWidth, 
							int 			GHeight, 
							int 			Colors, 
							unsigned char	Red[], 
							unsigned char	Green[], 
							unsigned char	Blue[], 
							vfunptr 		GetPlanes)
{
	int		BytesPerLine;
	int		Planes;
	int		BitsPerPixel;
	unsigned char	*buf;
	int		i;
	int		n;
	int		y;

	/*
	 * select number of planes and number of bits
	 * per pixel according to number of colors
	 */
	/*
	 * 16 colors or less are handled as 1 bit per pixel
	 * with 1, 2, 3 or 4 color planes.
	 * more than 16 colors are handled as 8 bits per pixel
	 * with 1 plane
	 */
	if (Colors > 16) {
		BitsPerPixel	= 8;
		Planes		= 1;
	} else {
		BitsPerPixel	= 1;
		if (Colors > 8)
			Planes = 4;
		else if (Colors > 4)
			Planes = 3;
		else if (Colors > 2)
			Planes = 2;
		else
			Planes = 1;
	}

        /*
         * Write the PCX header
         */
	Putbyte( 0x0a, fp);						// .PCX magic number
	Putbyte( 0x05, fp);						// PC Paintbrush version
	Putbyte( 0x01, fp);						// .PCX run length encoding
	Putbyte( BitsPerPixel, fp);				// bits per pixel

        Putword( 0, fp );						// x1 - image left
        Putword( 0, fp );						// y1 - image top
	Putword( GWidth-1, fp );				// x2 - image right
	Putword( GHeight-1, fp );				// y2 - image bottom

	Putword( GWidth, fp );					// horizontal resolution
	Putword( GHeight, fp );				// vertical resolution

        /*
         * Write out the Color Map for images with 16 colors or less
         */
	n = (Colors <= 16) ? Colors : 16;
        for (i = 0; i < n; ++i) {
                Putbyte( Red[i], fp );
                Putbyte( Green[i], fp );
                Putbyte( Blue[i], fp );
        }
        for (; i < 16; ++i) {
                Putbyte( 255, fp );
                Putbyte( 255, fp );
                Putbyte( 255, fp );
        }

	Putbyte( 0, fp);						// reserved byte

	Putbyte( Planes, fp);					// number of color planes

	BytesPerLine	= ((GWidth * BitsPerPixel) + 7) / 8;
	Putword( BytesPerLine, fp )	;			// number of bytes per scanline

	Putword( 1, fp);						// pallette info

	for (i = 0; i < 58; ++i)					// fill to end of header
		Putbyte( 0, fp );

	buf	= (unsigned char *)malloc( MAXPLANES * BytesPerLine );

	for (y = 0; y < GHeight; ++y) {
		(*GetPlanes)(y, GWidth, buf, Planes, BitsPerPixel);

		for (i = 0; i < Planes; ++i)
			PutPlane(fp, buf + (i * BytesPerLine), BytesPerLine);
	}

	/*
	 * color map for > 16 colors is at end of file
	 */
	if (Colors > 16) {
		Putbyte( 0x0c, fp);					// magic for 256 colors
        	for (i = 0; i < Colors; ++i) {
                	Putbyte( Red[i], fp );
                	Putbyte( Green[i], fp );
                	Putbyte( Blue[i], fp );
        	}
	        for (; i < MAXCOLORS; ++i) {
                	Putbyte( 255, fp );
                	Putbyte( 255, fp );
                	Putbyte( 255, fp );
        	}
	}
}

static void PutPlane (			NXStream	*fp, 
							unsigned char	*buf, 
							int 			Size )
{
	unsigned char		*end;
	int				c;
	int				previous;
	int				count;

	end	= buf + Size;

	previous = *buf++;
	count	 = 1;

	while (buf < end)
	{
		c = *buf++;
		if (c == previous && count < 63) {
			++count;
			continue;
		}

		if (count > 1 || (previous & 0xc0) == 0xc0) {
			count |= 0xc0;
			Putbyte ( count , fp );
		}
		Putbyte(previous, fp);
		previous = c;
		count	= 1;
	}

	if (count > 1 || (previous & 0xc0) == 0xc0) {
		count |= 0xc0;
		Putbyte ( count , fp );
	}
	Putbyte(previous, fp);
}

static unsigned long PixMap[8][16] =
{
	0x00000000L,	0x00000080L,	0x00008000L,	0x00008080L,
	0x00800000L,	0x00800080L,	0x00808000L,	0x00808080L,
	0x80000000L,	0x80000080L,	0x80008000L,	0x80008080L,
	0x80800000L,	0x80800080L,	0x80808000L,	0x80808080L,
};

static void ReadPlanes(		int			y, 
							int 			width, 
							unsigned char	*buf, 
							int 			planes, 
							int 			bits)
{
//	static pixel		**pixels;
	static int			first_time = 1;
	unsigned char		*plane0, *plane1, *plane2, *plane3;
	int				i, j, x;

	/*
	 * 256 color, 1 plane, 8 bits per pixel
	 */
	 plane0 = picData + width * y;
	if (planes == 1 && bits == 8) {
		for (x = 0; x < width; ++x)
//			buf[x] = ppm_lookupcolor( cht, &pixels[y][x] );
			buf[x] = *(plane0 + x);
		return;
	}

	/*
	 * must be 16 colors or less, 4 planes or less, 1 bit per pixel
	 */
	if (first_time) {
		for (i = 1; i < 8; ++i)
			for (j = 0; j < 16; ++j)
				PixMap[i][j] = PixMap[0][j] >> i;
		first_time = 0;
	}

	i = (width + 7) / 8;

	plane0	= buf;
	plane1	= plane0 + i;
	plane2	= plane1 + i;
	plane3	= plane2 + i;

	i	= 0;
	x	= 0;

	while ( x < width ) {
		register unsigned long	t;

		t	 = PixMap[0][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[1][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[2][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[3][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[4][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[5][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[6][ppm_lookupcolor( cht, &pixels[y][x++] )];
		t	|= PixMap[7][ppm_lookupcolor( cht, &pixels[y][x++] )];

		plane0[i] = t;
		plane1[i] = t >> 8;
		plane2[i] = t >> 16;
		plane3[i++] = t >> 24;
	}
}

/*
 * Write out a word to the PCX file
 */
static void Putword( 			int 			w, 
							NXStream	*fp)
{
        NXPutc( fp, w & 0xff );
        NXPutc( fp, (w / 256) & 0xff );
}

/*
 * Write out a byte to the PCX file
 */
static void Putbyte( 			int 			b, 
							NXStream	*fp )
{
        NXPutc( fp, b & 0xff );
}

/* The End */
